=begin
 *
 * File: sif.rb
 * Fundamental classes for the S-expression Interpreter Framework (SIF).
 * Version: 1.0
 * Copyright (C) 2006-2008 by Ariel Ortiz <ariel.ortiz@itesm.mx>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
=end

require 'sif_parser'

#--------------------------------------------------------------------
class Object

  def to_sexp
    return self.to_s
  end

  def callable?
    return self.methods.include?('call')
  end

end

#--------------------------------------------------------------------
class Array

  def rest
    return self.slice(1, self.length - 1)
  end

  def to_sexp
    return '(' +
      self.collect {|x|
        x.to_sexp}.join(' ') + ')'
  end

end

#--------------------------------------------------------------------
class Node

  @@special_forms = {}

  def Node.special_form(name)
    @@special_forms[name.to_sym] = self
  end

  def Node.build(data)
    if data.kind_of?(Integer)
      return IntegerNode.new(data)
    elsif data.kind_of?(Symbol)
      return VarRefNode.new(data)
    elsif data.kind_of?(Array)
      if data.empty?
        return EmptyListNode.new()
      elsif data.first.kind_of?(Symbol) and @@special_forms.has_key?(data.first)
        return @@special_forms[data.first].new(data.rest)
      else
        return CallNode.new(data)
      end
    else
      raise InterpreterError.new(
        "Unhandled data: '#{data}' of type #{data.class}")
    end
  end

end

#--------------------------------------------------------------------
class IntegerNode < Node

  def initialize(value)
    @value = value
  end

  def interpret(context)
    return @value
  end

end

#--------------------------------------------------------------------
class VarRefNode < Node

  def initialize(name)
    @name = name
  end

  def interpret(context)
    if context.has_key?(@name)
      return context[@name]
    else
      raise InterpreterError.new(
        "Variable '#{@name}' is not bound")
    end
  end

end

#--------------------------------------------------------------------
class EmptyListNode < Node

  def interpret(contex)
    return []
  end

end

#--------------------------------------------------------------------
class CallNode < Node

  def initialize(args)
    @procedure = Node.build(args.first)
    @args = args.rest.collect {|x| Node.build(x)}
  end

  def interpret(context)
    procedure = @procedure.interpret(context)
    if procedure.callable?
      args = @args.collect {|x| x.interpret(context)}
      procedure.call(args, context)
    else
      raise InterpreterError.new(
        "'#{procedure}' is not callable")
    end
  end

end

#--------------------------------------------------------------------
class Interpreter

  def Interpreter.eval(data, context)
    parsed_data = Parser.new(data)
    if block_given?
      parsed_data.each do |x|
        yield Node.build(x).interpret(context)
      end
    else
      parsed_data.each do |x|
        return Node.build(x).interpret(context)
      end
    end
  end

  def Interpreter.eval_file(file_name, context)
    str = File.read(file_name)
    eval(str, context) { }
  end

end

#--------------------------------------------------------------------
class Primitives

  @@primitives = {}

  def Primitives.include(*names, &procedure)
    names.each do |name|
      @@primitives[name.to_sym] = \
        if procedure.arity == 1
          lambda {|args, context| procedure.call(args)}
        else
          procedure
        end
    end
  end

  def Primitives.context
    @@primitives.dup
  end

end
